package com.netwit.common.service.impl;

import java.util.*;

import com.netwit.common.config.DataSourceContextHolder;
import com.netwit.common.mapper.CommonMapper;
import com.netwit.common.prop.DataSourceMappersProperties;
import com.netwit.common.service.CommonService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Service("CommonService")
public class CommonServiceImpl implements CommonService {

    private Logger logger = LoggerFactory.getLogger(CommonServiceImpl.class);

    @Autowired
    private DataSourceMappersProperties mappersProperties;

    @Autowired
    private CommonMapper mapper;

    @Override
    public Map<String, Object> list(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            String uri = (String) map.get("uri");
            uri = StringUtils.isEmpty(uri) ? map.get("apiPath") + "/" + alias : uri;

            map.put("mapper", mapper);
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            Map<String, Object> result = new LinkedHashMap<>();
            List<Map<String, Object>> res = mapper.list(map);
            for(Map<String, Object> m : res) {
                m.put("href", this.link(uri, m));
            }
            result.put("data", res);
            if (map.containsKey("page")) {
                map.put("rowCount", this.rowCount(map));
                map.put("rowcount", map.get("rowCount"));
                Map<String, Object> pageInfo = this.pageInfo(map);
                pageInfo.put("links", this.links(pageInfo, map));
                result.put("pageInfo", pageInfo);
            }
            return result;
        }
        return null;
    }

    @Override
    public Integer insert(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            map.put("href", this.link(map.get("apiPath") + "/" + alias, map));
            return mapper.insert(map);
        }
        return -1;
    }

    @Override
    public Map<String, Object> getOne(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            map.put("type", "ONE");

            Map<String, Object> result = new LinkedHashMap<>();
            ArrayList<Map<String, Object>> list = new ArrayList<>();
            Map<String, Object> res = mapper.getOne(map);
            if (res != null) {
                res.put("href", this.link(map.get("apiPath") + "/" + alias, res));
            }
            list.add(res);
            result.put("data", list);
            return result;
        }
        return null;
    }

    @Override
    public Integer update(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            map.put("href", this.link((String) map.get("apiPath") + "/" + alias, map));
            return mapper.update(map);
        }
        return -1;
    }

    @Override
    public Integer delete(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            // CommonMapper mapper = this.getMapperByAlias(alias, "");
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            return mapper.delete(map);
        }
        return -1;
    }

    @Override
    public List<Map<String, Object>> query(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            logger.info(map.toString());
            List<Map<String, Object>> list = mapper.query(map);
            logger.info(list.get(0).toString());
            return list;
        }
        return null;
    }

    @Override
    public Map<String, Object> queryOne(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            return mapper.getOne(map);
        }
        return null;
    }

    @Override
    public Integer insertEntity(Object entity, String tableName) {
        return null;
    }

    @Override
    public Integer updateEntity(Object entity, String tableName) {
        return null;
    }

    @Override
    public Integer batchInsertUpdateEntities(List<Object> list, String tableName) {
        return null;
    }

    @Override
    public Integer batchInsertUpdateMaps(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            this.setDataSourceByAlias(alias);
            map.put("tableName", this.getTableNameByAlias(alias));
            return mapper.batchInsertUpdateMaps(map);
        }
        return -1;
    }

    @Value("${mappers.primary-ds:basicDataSource}")
    private String primaryds;

    //根据表别名设置数据源
    @SuppressWarnings("unchecked")
    private void setDataSourceByAlias(String alias) {
        Map<String, Object> map = mappersProperties.getMap();
        for (String name : map.keySet()) {
            if (((Map<String, Object>)(map.get(name))).containsKey(alias)) {
                DataSourceContextHolder.setDb(name);
                return;
            }
        }
        DataSourceContextHolder.setDb(primaryds);
    }

    // 根据表别名从配置文件dbapi.yml中获取相对应的数据库mapper
    // @SuppressWarnings("unchecked")
    // private CommonMapper getMapperByAlias(String alias, String defaultMapperName) {
    //     Map<String, Object> map = (Map<String, Object>)mappersProperties.getMap().get("mappers");
    //     String mapperName = null;
    //     for (String name : map.keySet()) {
    //         if (StringUtils.isEmpty(mapperName)) { mapperName = name; }
    //         if (((Map<String, Object>)(map.get(name))).containsKey(alias)) {
    //             mapperName = name;
    //             break;
    //         }
    //     }
    //     return (CommonMapper)SpringContextUtil.getBean(mapperName);
    // }

    // 根据URI中的实体名称从配置文件中获取数据库表名
    @SuppressWarnings("unchecked")
    private String getTableNameByAlias(String alias) {
        Map<String, Object> map = mappersProperties.getMap();
        for (Object m : map.values()) {
            if (((Map<String, Object>)m).containsKey(alias)) return (String)((Map<String, Object>)m).get(alias);
        }
        return alias;
    }

    // 获取记录数
    private Long rowCount(Map<String, Object> map) {
        String alias = (String) map.get("alias");
        if (!StringUtils.isEmpty(alias)) {
            CommonMapper mapper = (CommonMapper) map.get("mapper");
            if (map.containsKey("type")) {
                map.replace("type", "COUNT");
            } else {
                map.put("type", "COUNT");
            }
            Map<String, Object> result = mapper.getOne(map);
            Long count = (Long) result.get("rowCount");
            map.remove("type");

            return count;
        }
        return null;
    }

    private Map<String, Object> pageInfo(Map<String, Object> map) {
        Integer rows = ((Long) map.get("rowCount")).intValue();
        rows = rows == null ? ((Long) map.get("rowcount")).intValue() : rows;
        String  pageString = (String) map.get("page");
        String  sizeString = (String) map.get("size");
        Integer page = StringUtils.isEmpty(pageString) ? 1 : Integer.valueOf(pageString);
        Integer size = StringUtils.isEmpty(sizeString) ? 1 : Integer.valueOf(sizeString);
        Integer totalPage = rows % size == 0 ? rows / size : rows / size + 1;
        // Math.ceil(1.01) = 2; Math.floor(1.01) = 1; Math.round() - 四舍五入；

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("page", page);
        result.put("pagenum", page);
        result.put("size", size);
        result.put("pagesize", size);
        result.put("totalPage", totalPage);
        result.put("isFirst", page == 1);
        result.put("hasPrev", page > 1);
        result.put("hasNext", page < totalPage);
        result.put("isLast", Objects.equals(page, totalPage));
        
        return result;
    }

    private Map<String, Object> links(Map<String, Object> pageInfo, Map<String, Object> map) {
        Integer page  = (Integer) pageInfo.get("page");
        Integer size  = (Integer) pageInfo.get("size");
        Integer pages = (Integer) pageInfo.get("totalPage");
        Integer prevPage = page > 1 ? page - 1 : 1;
        Integer nextPage = page >= pages ? pages : page + 1;
        String apiPath   = map.get("apiPath") + "/" + map.get("alias");
        String queryString = (String) map.get("queryString");
        apiPath += StringUtils.isEmpty(queryString) ? "" : "?" + map.get("queryString");

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("self",  apiPath + (apiPath.contains("?") ? "&" : "?") + "page=" + page + "&size=" + size);
        result.put("first", apiPath + (apiPath.contains("?") ? "&" : "?") + "page=1&size=" + size);
        result.put("last",  apiPath + (apiPath.contains("?") ? "&" : "?") + "page=" + pages + "&size=" + size);
        result.put("prev",  apiPath + (apiPath.contains("?") ? "&" : "?") + "page=" + prevPage + "&size=" + size);
        result.put("next",  apiPath + (apiPath.contains("?") ? "&" : "?") + "page=" + nextPage + "&size=" + size);

        return result;
    }

    private String link(String uri, Map<String, Object> map) {
        String key = map.containsKey("uuid") ? "uuid" : map.keySet().iterator().next();
        return uri.contains("/" + map.get(key)) ? uri : uri + "/" + map.get(key);
    }
}
